﻿%define pg.RootNamespace ParserGenerator
%define pg.SyntacticAnalysisNamespace ParserGenerator.Languages.LocalTypeInference
%define pg.SourceContextNamespace ParserGenerator.Input
%define pg.OutputNamespace ParserGenerator.Output

%define pg.AllowAutoDefineLiteralTokens True
%define pg.UsePythonLikeIndentation False
%define pg.indented False

%define pg.parser.classname LocalTypeInferenceProgramParser
%define pg.parser.namespace ParserGenerator.Languages.LocalTypeInference

%define pg.parser.usings (
    ParserGenerator.Collections,
)

%goal Expression
%goal InteractiveTypeChecking

%keyword 'def'
%keyword 'object'
%keyword 'null'
%keyword 'bool'
%keyword 'int'
%keyword 'forall'
%keyword 'false'
%keyword 'true'

%token Identifier ("identifier") : Identifier
%token IntLiteral ("int literal") : IntLiteral

Identifier = (name : string)
IntLiteral = (value : System.Numerics.BigInteger)

Parameter = (name : Identifier, `type` : Type)
ParameterList = (items : [Parameter]+)
ArgumentList = (items : [Expression]+)

TypeParameter = (name : Identifier)
TypeParameterList = (items : [TypeParameter]+)
TypeArgumentList = (items : [Type]+)

Expression:
    Variable (name : Identifier)
    `False`
    `True`
    IntLiteral (value : IntLiteral)
    Group (operand : Expression)
    Unary (op : UnaryOp, operand : Expression)
    Binary (left : Expression, op : BinaryOp, right : Expression)
    Function (typeParameters : TypeParameterList?, parameters : ParameterList, body : Expression)
    Call (function : Expression, typeArguments : TypeArgumentList?, arguments : ArgumentList)

UnaryOp:
    Positive
    Negative
    LogicalNot

BinaryOp:
    Add
    Subtract
    Multiply
    Divide
    Modulo
    EqualTo
    NotEqualTo
    LessThan
    LessEqual
    GreaterThan
    GreaterEqual
    LogicalAnd
    LogicalOr

Type:
    Variable (name : Identifier)
    Object
    Null
    Bool
    Int
    Function (typeParameters : TypeParameterList?, sources : [Type]+, target : Type)

VariableBinding:
    Value (name : Identifier, `type` : Type)
    Type (name : Identifier)
    TypeSubstitution (name : Identifier, `type` : Type)

%%

Parameter as Parameter:
    Identifier ':' Type => Parameter.New($1, $3, @@)
ParameterList as ParameterList:
    Parameter %+ ',' => ParameterList.New($1, @@)
ArgumentList as ArgumentList:
    Expression %+ ',' => ArgumentList.New($1, @@)

TypeParameter as TypeParameter:
    Identifier => TypeParameter.New($1, @@)
TypeParameterList as TypeParameterList:
    TypeParameter %+ ',' => TypeParameterList.New($1, @@)
TypeArgumentList as TypeArgumentList:
    Type %+ ',' => TypeArgumentList.New($1, @@)

PrimaryExpression as Expression:
    Identifier => Expression.NewVariable($1, @@)
    'false' => Expression.NewFalse(@@)
    'true' => Expression.NewTrue(@@)
    IntLiteral => Expression.NewIntLiteral($1, @@)
    'def' '(' ParameterList ')' '{' Expression '}' => Expression.NewFunction(None, $3, $6, @@)
    'def' '[' TypeParameterList ']' '(' ParameterList ')' '{' Expression '}' => Expression.NewFunction($3, $6, $9, @@)
    '(' Expression ')' => Expression.NewGroup($2, @@)
    PrimaryExpression '(' ArgumentList ')' => Expression.NewCall($1, None, $3, @@)
    PrimaryExpression '[' TypeArgumentList ']' '(' ArgumentList ')' => Expression.NewCall($1, $3, $6, @@)
UnaryExpression as Expression:
    PrimaryExpression => $1
    '+' UnaryExpression => Expression.NewUnary(UnaryOp.NewPositive(@1), $2, @@)
    '-' UnaryExpression => Expression.NewUnary(UnaryOp.NewNegative(@1), $2, @@)
    '!' UnaryExpression => Expression.NewUnary(UnaryOp.NewLogicalNot(@1), $2, @@)
MultiplicativeExpression as Expression:
    UnaryExpression => $1
    MultiplicativeExpression '*' UnaryExpression => Expression.NewBinary($1, BinaryOp.NewMultiply(@2), $3, @@)
    MultiplicativeExpression '/' UnaryExpression => Expression.NewBinary($1, BinaryOp.NewDivide(@2), $3, @@)
    MultiplicativeExpression '%' UnaryExpression => Expression.NewBinary($1, BinaryOp.NewModulo(@2), $3, @@)
AdditiveExpression as Expression:
    MultiplicativeExpression => $1
    AdditiveExpression '+' MultiplicativeExpression => Expression.NewBinary($1, BinaryOp.NewAdd(@2), $3, @@)
    AdditiveExpression '-' MultiplicativeExpression => Expression.NewBinary($1, BinaryOp.NewSubtract(@2), $3, @@)
RelationalExpression as Expression:
    AdditiveExpression => $1
    AdditiveExpression '<' AdditiveExpression => Expression.NewBinary($1, BinaryOp.NewLessThan(@2), $3, @@)
    AdditiveExpression '<=' AdditiveExpression => Expression.NewBinary($1, BinaryOp.NewLessEqual(@2), $3, @@)
    AdditiveExpression '>' AdditiveExpression => Expression.NewBinary($1, BinaryOp.NewGreaterThan(@2), $3, @@)
    AdditiveExpression '>=' AdditiveExpression => Expression.NewBinary($1, BinaryOp.NewGreaterEqual(@2), $3, @@)
EqualityExpression as Expression:
    RelationalExpression => $1
    RelationalExpression '==' RelationalExpression => Expression.NewBinary($1, BinaryOp.NewEqualTo(@2), $3, @@)
    RelationalExpression '!=' RelationalExpression => Expression.NewBinary($1, BinaryOp.NewNotEqualTo(@2), $3, @@)
LogicalAndExpression as Expression:
    EqualityExpression => $1
    LogicalAndExpression '&&' EqualityExpression => Expression.NewBinary($1, BinaryOp.NewLogicalAnd(@2), $3, @@)
LogicalOrExpression as Expression:
    LogicalAndExpression => $1
    LogicalOrExpression '||' LogicalAndExpression => Expression.NewBinary($1, BinaryOp.NewLogicalOr(@2), $3, @@)
Expression as Expression:
    LogicalOrExpression => $1

PrimaryType as Type:
    Identifier => Type.NewVariable($1, @@)
    'object' => Type.NewObject(@@)
    'null' => Type.NewNull(@@)
    'bool' => Type.NewBool(@@)
    'int' => Type.NewInt(@@)
    '(' Type ')' => $2
Type as Type:
    PrimaryType => $1
    PrimaryType %+ '*' '->' Type => Type.NewFunction(None, $1, $3, @@)
    'forall' TypeParameterList '.' PrimaryType %+ '*' '->' Type => Type.NewFunction($2, $4, $6, @@)

InteractiveTypeChecking:
    '?' Type '<:' Type =>: System.Console.WriteLine("yes" if LocalTypeInferenceTypeChecker.IsSubtypeOf($2, $4) else "no")
    '?' Type '\\/' Type =>: System.Console.WriteLine(LocalTypeInferenceTypeChecker.JoinType($2, $4))
    '?' Type '/\\' Type =>: System.Console.WriteLine(LocalTypeInferenceTypeChecker.MeetType($2, $4))
    Expression =>: System.Console.WriteLine(LocalTypeInferenceTypeChecker.TypeCheck($1))

%%

nl = [ '\n' '\v' '\f' '\r' '\x85' '\u2028' '\u2029' ]
ws = [ '\t' ' ' '\xa0' '\u2000'-'\u200a' '\u202f' '\u205f' '\u3000' ]+
nnl = [^ '\n' '\v' '\f' '\r' '\x85' '\u2028' '\u2029' ]

lower      = ['a'-'z']
upper      = ['A'-'Z']
digit      = ['0'-'9']
letter     = lower | upper | '_'
identifier = letter (letter | digit)*

decimal_integer = digit+
hexadecimal_integer = '0x' ['0'-'9' 'A'-'F' 'a'-'f']+

<*>:
    nl =>: pass
    ws =>: pass

    identifier =>: ScanIdentifierName(); yield Identifier
    decimal_integer =>: ScanIntegerValue(); yield IntLiteral
